package org.wevil.message;

import org.wevil.util.CRC16Util;
import lombok.Getter;
import lombok.Setter;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 651协议，ASCII 字符编码 M1/M2/M4 模式上行帧结构定义
 *
 * @since 2022/4/7
 * */
@Getter
@Setter
public class Message651UP {

    /** 帧起始符。 1个字节， 默认为 */
    private static String SOH = "\u0001";

    /** 中心站地址 2个字节，1 字节 HEX 转换为 2 个 ASCII 字符传输，范围为 1～255。
     指以省（或流域机构）为单元，为县、市级以上分中心
     分配的中心站地址。
     */
    private String centerAddress;

    /**
     * 遥测站地址 10个字节，转换为 10 个 ASCII 字符传输。
     * */
    private String rtuAddress;

    /** 密码 4个字节  2字节HEX编码，转换为4个ASCII字符传输 */
    private String password;

    /** 功能码 2个字节 需将 1 个字节功能码转换为 2 个 ASCII 字符传输。
     * 2F -> 链路维持报。遥测站定时向中心站发送链路维持信息
     * 32 -> 遥测站定时报
     * 33 -> 遥测站加报报。报送由时间或事件触发的加报实时数据
     * 37 -> 中心站查询遥测站实时数据
     * 42 -> 中心站修改遥测站运行参数配置表。遥测站运行参数配置
     * 43 -> 中心站读取遥测站运行参数配置表/遥测站自报运行参数配置表*/
    private String functionCode;

    /** 报文上行标识及长度 用 2 字节 HEX 编码转换为 4 个 ASCII 字符传输。
     * 第 1 个字符用作上下行标识（0 表示上行，8 表示下行）；
     * 其余3 个字符表示报文正文长度，表示报文起始符之后、报
     文结束符之前的报文字节数，允许长度为 0001～4095。*/
    private String length;

    /** 报文起始符 1个字节 默认为 */
    private static String STX = "\u0002";

    /** 报文正文 长度不定 自报树、响应帧内容等
     * 心跳报文内容：
     * 2字节HEX码，范围1～65535
     * 6字节BCD码，YYMMDDHHmmSS
     * 举例：4D3D220318120033
     * */
    private String content;

    /** 报文结束符 1个字节 默认为 */
    private static String ETX = "\u0003";

    /** 校验码 4个字节 2字节HEX编码，转换为4个ASCII字符传输；校验码
     前所有字节的 CRC 校验，生成多项式：X16+X15+X2+1，高位字节在前，低位字节在后。*/
    private String crc;

    /**
     * 检查是否651上行报文
     * */
    public static boolean check(String message){
        // 0. 检查长度，651协议上行报文长度最小也要有30个字符
        int messageLength = message.length();
        if(messageLength < 30){
            System.out.println("报文[" + message + "]不是651上行报文。长度不够");
            return false;
        }
        // 1. 检查帧起始符是否
        String SOH = message.substring(0, 1);
        if(!SOH.equals(Message651UP.SOH)){
            System.out.println("报文[" + message + "]不是651上行报文。帧起始符不是SOH");
            return false;
        }
        // 2. 检查上行标识
        String length = message.substring(19, 23);                  // 报文上行标识及长度
        String symbol = length.substring(0, 1);                     // 上行标识，默认为0
        if(!symbol.equals("0")){
            System.out.println("报文[" + message + "]不是651上行报文。上行标识不是0");
            return false;
        }
        // 3. 检查报文起始符
        String lengthHex = length.substring(1);                    // 报文长度16进制
        int contentLength = Integer.parseInt(lengthHex, 16);  // 报文内容长度
        String STX = message.substring(23, 24);                     // 报文起始符
        if(!STX.equals(Message651UP.STX)){
            System.out.println("报文[" + message + "]不是651上行报文。报文起始符不是STX");
            return false;
        }
        // 4. 检查报文结束符
        String ETX = message.substring(24+contentLength, 24+contentLength+1); // 报文结束符
        if(!ETX.equals(Message651UP.ETX)){
            System.out.println("报文[" + message + "]不是651上行报文。报文结束符不是ETX");
            return false;
        }
        // 5. CRC校验
        String CRC_MESSAGE = message.substring(0, message.length()-4); // 校验的报文
        String CRC = message.substring(message.length()-4);     // 报文内的校验码
        int i = CRC16Util.CRC16_MODBUS(CRC_MESSAGE.getBytes()); // 生成的校验码
        byte[] bytes1 = CRC16Util.intTo2Byte(i); // int类型转换为byte类型 (这里的bytes[0] bytes[1] 就是最终得到的CRC校验码)
        String str = CRC16Util.encodeHexStr(bytes1).toUpperCase(); // byte转换为可查看的String类型
        if(!str.equals(CRC)){
            System.out.println("报文[" + message + "]CRC校验失败。报文CRC[" + CRC + "], 校验的CRC[" + str + "]");
            return false;
        }
        return true;
    }

    /**
     * 解析上行报文，返回解析结果
     *
     * @since 2022/4/8
     * */
    public static Message651UP parse(String content) {
        Message651UP message = new Message651UP();
        int contentLength = Integer.parseInt(content.substring(20, 23), 16);  // 报文内容长度
        message.setCenterAddress(content.substring(1, 3));  // 中心站地址 2-3
        message.setRtuAddress(content.substring(3, 13));    // rtu编码 4-13
        message.setPassword(content.substring(13, 17));     // 密码 14-17
        message.setFunctionCode(content.substring(17, 19)); // 功能码 18-19
        message.setLength(content.substring(19, 23));       // 长度 20-23
        message.setContent(content.substring(23, 23+contentLength)); // 报文内容 长度不定
        message.setCrc(content.substring(content.length()-4)); // CRC 报文最后4个字符
        return message;
    }

    /**
     * 报文对象转ASCII字符串
     *
     * @since 2022/4/8
     * */
    public String obj2Str(Message651UP message){
        return Message651UP.SOH +
                message.getCenterAddress() +
                message.getRtuAddress() +
                message.getPassword() +
                message.getFunctionCode() +
                message.getLength() +
                Message651UP.STX +
                message.getContent() +
                Message651UP.ETX +
                message.getCrc();
    }

    /**
     * 获取报文里的内容
     * @param content 报文正文。例如:0083230114182250ST 0000000111 H TT 2301141822 PT 2.5 PJ 2.5 VT 11.79
     * @return 根据指定的字段返回值，比如PT，返回PT后跟着的2.5
     * */
    public static Map<String, String> getContent(String content, List<String> fields){
        String[] strings = content.split(" ");
        Map<String, String> result = new HashMap<>();
        for(int i=0; i<strings.length; i++){
            if(fields.contains(strings[i])){
                result.put(strings[i], strings[i+1]);
            }
        }
        return result;
    }
}
